<template>
  <div class="prop-form">
    <div class="prop-form-content pull-left">
      <Form
        class="form"
        ref="form"
        label-position="top"
        v-if="isUpdate"
        :model="formObj"
        :rules="rules">
        <draggable
          class="draggable"
          v-model="formData"
          v-if="isDrag"
          :options="formOption"
          @start="start"
          @end="end"
          @choose="choose">
          <div
            class="list"
            v-for="(item, index) in formData"
            :key="index">
            <form-item
              ref="form-item"
              v-model="formObj[formItem['id']]"
              v-for="(formItem, formIndex) in item.children"
              :isDrag="isDrag"
              :key="formIndex"
              :formData="formData"
              :parentIndex="index"
              :item="formItem"
              :index="formIndex"
              @topClick="topClick(formItem, formIndex, index)"
              @bottomClick="bottomClick(formItem, formIndex, index)"
              @deleteClick="deleteClick(formIndex, index)"
              @leftChangeClick="leftChangeClick(formItem, formIndex, index)"
              @rightChangeClick="rightChangeClick(formItem, formIndex, index)"
              @formItemClick="formItemClick(formItem, index)"></form-item>
          </div>
        </draggable>
        <div class="draggable" v-if="!isDrag">
          <div
            class="list"
            v-for="(item, index) in formData"
            :key="index">
            <form-item
              ref="form-item"
              v-model="formObj[formItem['id']]"
              v-for="(formItem, formIndex) in item.children"
              :isDrag="isDrag"
              :key="formIndex"
              :formData="formData"
              :parentIndex="index"
              :item="formItem"
              :index="formIndex"
              @contentChange="contentChange"
              @tableValueChange="tableValueChange"
              @refreshOperation="refreshOperation"
              @formItemClick="formItemClick(formItem, index)"></form-item>
          </div>
        </div>
      </Form>
    </div>
  </div>
</template>

<script>
  import draggable from 'vuedraggable'
  import FormItem from './form-item'
  export default {
    props: {
      isDrag: {
        type: Boolean,
        default: true
      },
      value: {
        type: Array,
        default: () => []
      }
    },
    components: {
      'form-item': FormItem,
      draggable
    },
    created () {
      this.activeItem = {}
      this.formIndex = 0
      this.activeIndex = 0
      this.initData()
    },
    data () {
      return {
        isUpdate: true,
        formOption: {
          animation: 150
        },
        formData: [],
        formObj: {},
        rules: {}
      }
    },
    methods: {
      refreshOperation (item) {
        let str = ''
        const { value } = item.operation
        value.map(item => {
          switch (item.type) {
            case 'keys':
              str += this.formObj[item.id]
              break
            case 'number':
            case 'symbol':
              str += item.str
              break
          }
        })
        const num = this.evil(str)

        this.$set(item.options, 'value', num)
        this.formObj[item.id] = num
        this.emitData()
      },
      tableValueChange ({ id, index, key, value }) {
        for (let i = 0; i < this.formData.length; i++) {
          for (let k = 0; k < this.formData[i].children.length; k++) {
            const listItem = this.formData[i].children[k]
            if (listItem.id === id) {
              listItem.options.data[index][key] = value
              break
            }
          }
        }
      },
      contentChange () {
        this.emitData()
      },
      topClick (formItem, formIndex, index) {
        this.formData[index - 1].children.push(formItem)
        if (this.formData[index].children.length > 1) {
          this.formData[index].children.splice(formIndex, 1)
          this.emitData()
          return
        }
        this.formData.splice(index, 1)
        this.emitData()
      },
      bottomClick (formItem, formIndex, index) {
        const cloneItem = Object.assign({}, formItem)
        const data = {
          children: [cloneItem]
        }
        this.formData.splice(index + 1, 0, data)
        this.formData[index].children.splice(formIndex, 1)
        this.emitData()
      },
      deleteClick (formIndex, index) {
        if (this.formData[index].children.length <= 1) {
          this.formData.splice(index, 1)
        } else {
          this.formData[index].children.splice(formIndex, 1)
        }
        this.emitData()
      },
      leftChangeClick (formItem, formIndex, index) {
        const cloneItem = Object.assign({}, formItem)
        this.formData[index].children.splice(formIndex, 1)
        this.formData[index].children.splice(formIndex - 1, 0, cloneItem)
        this.emitData()
      },
      rightChangeClick (formItem, formIndex, index) {
        const cloneItem = Object.assign({}, formItem)
        this.formData[index].children.splice(formIndex, 1)
        this.formData[index].children.splice(formIndex + 1, 0, cloneItem)
        this.emitData()
      },
      formItemClick (formItem, index) {
        this.activeItem = formItem
        this.activeIndex = index
        this.formData.map(listItem => {
          listItem.children.map(child => {
            if (child['_custom_active']) {
              this.$set(child, '_custom_active', false)
            }
          })
        })
        this.$set(formItem, '_custom_active', true)
        this.$emit('listClick', formItem)
      },
      choose (el) {

      },
      start (el) {

      },
      end (el) {
        if (el.newIndex !== el.oldIndex) {
          this.emitData()
        }
      },
      filterData (obj) {
        if (typeof obj === 'object') {
          if (Array.isArray(obj)) {
            obj.map(objItem => {
              this.filterData(objItem)
            })
          } else {
            for (const key in obj) {
              if (typeof obj[key] === 'object') {
                if (Array.isArray(obj[key])) {
                  obj[key].map(objKeyItem => {
                    this.filterData(objKeyItem)
                  })
                } else {
                  this.filterData(obj[key])
                }
              } else {
                const req = /^_custom_/g
                if (req.test(key)) {
                  delete obj[key]
                }
              }
            }
          }
        }
      },
      addData (item) {
        const cloneItem = this.cloneObj(item)
        cloneItem['id'] = this.getUUID()
        const data = {
          children: [cloneItem]
        }
        let index = 0
        this.formData.map((formItem, formIndex) => {
          formItem.children.map(child => {
            if (child['_custom_active']) {
              index = formIndex
            }
          })
        })
        this.formData.splice(index, 0, data)
        this.emitData()
      },
      initData () {
        if (!Array.isArray(this.value)) {
          throw new Error('props list is not a Array')
        }
        this.formData = this.cloneObj(this.value)
        this.formData.map((item, index) => {
          item.children.map((child, childIndex) => {
            if (index === 0 && childIndex === 0 && this.isDrag) {
              this.$set(child, '_custom_active', true)
              this.activeItem = child
            }
            let defaultValue = ''

            switch (child.type) {
              case 'InputNumber':
                defaultValue = null
                break
              case 'CheckboxGroup':
                defaultValue = []
                break
              case 'Checkbox':
              case 'Radio':
                defaultValue = true
                break
            }

            this.$set(this.formObj, child.id, defaultValue)

            if (
              !this.isDrag &&
              child.isRequired
            ) {
              let rule = [
                { required: true, message: `${child.label}不能为空`, trigger: 'change' }
              ]
              switch (child.type) {
                case 'InputNumber':
                case 'Operation':
                  rule = [
                    { type: 'number', required: true, message: `${child.label}不能为空`, trigger: 'change' }
                  ]
                  break
                case 'Select':
                case 'DatePicker':
                case 'Radio':
                  rule = [
                    { required: true, message: `${child.label}不能为空`, trigger: 'change' }
                  ]
                  break
                case 'CheckboxGroup':
                  rule = [
                    { type: 'array', required: true, message: `${child.label}不能为空`, trigger: 'change' }
                  ]
                  break
              }
              this.$set(this.rules, child.id, rule)
            }
          })
        })
      },
      emitData () {
        this.$emit('input', this.formData)

        // this.isUpdate = false
        // this.$nextTick(() => {
        //   this.isUpdate = true
        // })
      },
      cloneObj (obj) {
        if (typeof obj !== 'object') {
          return obj
        }
        var s = {}
        if (obj.constructor === Array) {
          s = []
        }
        for (var i in obj) {
          s[i] = this.cloneObj(obj[i])
        }
        return s
      },
      getUUID () {
        let d = new Date().getTime()
        const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
          var r = (d + Math.random() * 16) % 16 | 0
          d = Math.floor(d / 16)
          return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
        })
        return uuid
      },
      testOperation (string) {
        // 剔除空白符
        string = string.replace(/\s/g, '')

        // 错误情况，空字符串
        if (string === '') {
          return false
        }

        // 错误情况，运算符连续
        if (/[\+\-\*\/]{2,}/.test(string)) {
          return false
        }

        // 空括号
        if (/\(\)/.test(string)) {
          return false
        }

        // 错误情况，括号不配对
        var stack = []
        for (var i = 0, item; i < string.length; i++) {
          item = string.charAt(i)
          if (item === '(') {
            stack.push('(')
          } else if (item === ')') {
            if (stack.length > 0) {
              stack.pop()
            } else {
              return false
            }
          }
        }
        if (stack.length !== 0) {
          return false
        }

        // 错误情况，(后面是运算符
        if (/\([\+\-\*\/]/.test(string)) {
          return false
        }

        // 错误情况，)前面是运算符
        if (/[\+\-\*\/]\)/.test(string)) {
          return false
        }

        // 错误情况，(前面不是运算符
        if (/[^\+\-\*\/]\(/.test(string)) {
          return false
        }

        // 错误情况，)后面不是运算符
        if (/\)[^\+\-\*\/]/.test(string)) {
          return false
        }

        return true
      },
      evil (fn) {
        const Fn = Function // 一个变量指向Function，防止有些前端编译工具报错
        return new Fn('return ' + fn)()
      },
      _addOperation (obj) {
        return new Promise((resolve, reject) => {
          if (typeof obj !== 'object') {
            throw new Error('operation is not object')
          }

          if (this.activeItem.type !== 'Operation') {
            return
          }

          const cloneObj = this.cloneObj(obj)
          const { value } = obj
          let str = ''
          value.map(item => {
            switch (item.type) {
              case 'keys':
                str += this.formObj[item.id]
                this.formData.map(child1 => {
                  child1.children.map(child2 => {
                    if (child2.id === item.id) {
                      if (child2.operationParent === undefined) {
                        child2.operationParent = []
                      }
                      if (child2.operationParent.indexOf(this.activeItem.id) === -1) {
                        child2.operationParent.push(this.activeItem.id)
                      }
                    }
                  })
                })
                break
              case 'number':
              case 'symbol':
                str += item.str
                break
            }
          })

          if (!this.testOperation(str)) {
            resolve({
              code: 1,
              message: '运算式校验不通过'
            })
            return
          }

          resolve({
            code: 0,
            message: '运算式校验通过'
          })

          const num = this.evil(str)
          this.activeItem.operation = cloneObj
          this.$set(this.activeItem.options, 'value', num)
          this.formObj[this.activeItem.id] = num
          this.emitData()
        })
      },
      _getFilterData () {
        // 过滤_custom_开头的自定义参数
        const obj = {}
        const arr = []
        const cloneData = this.cloneObj(this.formData)
        cloneData.map(item => {
          this.filterData(item)
        })
        cloneData.map(item => {
          item.children.map(child => {
            arr.push(child)
          })
        })
        obj.web = cloneData
        obj.server = arr
        return obj
      },
      _formValidate () {
        // 校验整个表单
        if (this.isDrag) {
          throw new Error('isDrag is not validate')
        }

        return new Promise((resolve, reject) => {
          this.$refs['form'].validate(valid => {
            resolve(valid)
          })
        })
      },
      _formResetFields () {
        // 清除整个表单校验
        if (this.isDrag) {
          throw new Error('isDrag is not resetFields')
        }

        this.$refs['form'].resetFields()
      }
    }
  }
</script>

<style lang="scss" scoped>
  .prop-form {
    width: 100%;
    height: 100%;
    overflow: hidden;
    .prop-form-content {
      width: 100%;
      height: 100%;
      overflow: auto;
      position: absolute;
      top: 0;
      left: 0;
      > .form {
        width: 700px;
        box-sizing: border-box;
        position: absolute;
        left: 0;
        right: 0;
        margin: 20px auto;
        > .draggable {
          > .list {
            display: flex;
            // border-bottom: 1px solid lightgray;
            &:nth-last-of-type(1) {
              // border-bottom: none;
            }
          }
        }
      }
    }
  }
</style>

